package Utils.web;

import Utils.FsUtils;
import Utils.NumberUtils;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY;

/**
 * 上传下载工具类
 *
 * @author d11yu
 */
public class WebUtils {
    /**
     * 将文件上传至服务器
     *
     * @param request         HttpServletRequest对象
     * @param directoryTarget 服务器中的目标文件夹路径
     */
    public static void upload(HttpServletRequest request, String directoryTarget) {
        if (ServletFileUpload.isMultipartContent(request)) {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload sfu = new ServletFileUpload(factory);
            try {
                List<FileItem> fileItems = sfu.parseRequest(request);
                for (FileItem fileItem : fileItems) {
                    if (!fileItem.isFormField()) {
                        // 上传文件
                        fileItem.write(new File(String.valueOf(Paths.get(directoryTarget, fileItem.getName()))));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 将文件上传至服务器（适用SpringMVC），不改名文件名。
     *
     * @param session HttpSession
     * @param file    待上传的文件
     * @param destDir 目标文件夹，如没有，则会创建
     * @throws IOException
     */
    public static void upload(HttpSession session, MultipartFile file, String destDir) throws IOException {
        upload(session, file, destDir, file.getOriginalFilename());
    }

    /**
     * 将文件上传至服务器（适用SpringMVC），用随机生成的UUID来重命名文件。
     *
     * @param session HttpSession
     * @param file    待上传的文件
     * @param destDir 目标文件夹，如没有，则会创建
     * @throws IOException
     */
    public static void uploadUUID(HttpSession session, MultipartFile file, String destDir) throws IOException {
        String filename = file.getOriginalFilename();
        String suffix = FsUtils.getSuffix(filename);
        upload(session, file, destDir, NumberUtils.generateUUID() + "." + suffix);
    }

    /**
     * 将文件上传至服务器（适用SpringMVC）
     *
     * @param session     HttpSession
     * @param file        待上传的文件
     * @param destDir     目标文件夹，如没有，则会创建
     * @param newFileName 新文件名（需要包含文件后缀）
     * @throws IOException
     */
    public static void upload(HttpSession session, MultipartFile file, String destDir, String newFileName) throws IOException {
        ServletContext servletContext = session.getServletContext();
        String destPath = servletContext.getRealPath(destDir);
        FsUtils.createDir(destDir);
        String finalPath = destPath + File.separator + newFileName;
        file.transferTo(new File(finalPath));
    }

    /**
     * 下载文件，仅限工程路径下的文件
     *
     * @param request  HttpServletRequest对象
     * @param response HttpServletResponse对象
     * @param path     文件路径
     * @throws IOException
     */
    public static void download(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
        download(request, response, path, "ISO8859-1");
    }

    /**
     * 下载文件，仅限工程路径下的文件
     *
     * @param request     HttpServletRequest对象
     * @param response    HttpServletResponse对象
     * @param path        文件路径
     * @param charsetName 文件名的编码，中文为"ISO8859-1"
     * @throws IOException
     */
    public static void download(HttpServletRequest request, HttpServletResponse response, String path, String charsetName) throws IOException {
        ServletContext servletContext = request.getServletContext();
        // 获取要下载文件的类型
        String mimeType = servletContext.getMimeType(path);
        // 回传前，通过响应头告诉客户端返回的数据类型
        response.setContentType(mimeType);
        // 还要告诉客户端收到的数据是用于下载的
        // Content-Disposition响应头，表示收到的数据怎么处理
        // attachment表示附件，表示下载使用
        // filename表示指定下载的文件名，同时解决中文乱码问题。
        String strTemp = "attachment;filename=" + new String(FsUtils.getName(path).getBytes(StandardCharsets.UTF_8), charsetName);
        // 以下代码在chrome、和IE浏览器中适用
//        String strTemp = "attachment;filename=" + URLEncoder.encode(FsUtils.getName(path),"UTF-8");
        // 以下代码在老版的FireFox浏览器中适用
//        String strTemp = "attachment;filename==?UTF-8?B?" + Base64Utils.getInstance().encode(FsUtils.getName(path));
        response.setHeader("Content-Disposition", strTemp);
        try (InputStream is = servletContext.getResourceAsStream(path);
             ServletOutputStream os = response.getOutputStream()) {
            IOUtils.copy(is, os);
        }
    }

    /**
     * 下载文件，仅限工程路径下的文件，返回ResponseEntity<byte[]>，默认使用"ISO8859-1"编码。
     *
     * @param session HttpSession
     * @param path    文件路径，例"/static/img/1.jpg"
     * @return
     * @throws IOException
     */
    public static ResponseEntity<byte[]> download(HttpSession session, String path) throws IOException {
        return download(session, path, "ISO8859-1");
    }

    /**
     * 下载文件，仅限工程路径下的文件，返回ResponseEntity<byte[]>。
     *
     * @param session     HttpSession
     * @param path        文件路径，例"/static/img/1.jpg"
     * @param charsetName 文件名的编码，中文为"ISO8859-1"
     * @return
     * @throws IOException
     */
    public static ResponseEntity<byte[]> download(HttpSession session, String path, String charsetName) throws IOException {
        ServletContext servletContext = session.getServletContext();
        String realPath = servletContext.getRealPath(path);
        FileInputStream is = new FileInputStream(realPath);
        byte[] bytes = new byte[is.available()];
        is.read(bytes);
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment;filename=" + new String(FsUtils.getName(path).getBytes(StandardCharsets.UTF_8), charsetName));
        HttpStatus statusCode = HttpStatus.OK;
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
        is.close();
        return responseEntity;
    }

    /**
     * 用BeanUtils将参数注入Java Bean对象中。
     * 需要引入的依赖有：
     *
     * @param properties 存放属性的map，web层传入参数请用request.getParameterMap()
     * @param clazz      Java Bean对象类
     * @param <T>
     * @return
     */
    public static <T> T copyParamsToBean(Map properties, Class<T> clazz) {
        try {
            T bean = clazz.newInstance();
            BeanUtils.populate(bean, properties);
            return bean;
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据名称查找cookie，如果没找到，则返回null。
     *
     * @param name    要查找的cookie的名称
     * @param request HttpServletRequest对象
     * @return
     */
    public static Cookie findCookie(String name, HttpServletRequest request) {
        return findCookie(name, request.getCookies());
    }

    /**
     * 根据名称查找cookie，如果没找到，则返回null。
     *
     * @param name    要查找的cookie的名称
     * @param cookies cookie数组
     * @return
     */
    public static Cookie findCookie(String name, Cookie[] cookies) {
        if (name == null || "".equals(name) || cookies == null || cookies.length == 0) {
            return null;
        }
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals(name)) {
                return cookie;
            }
        }
        return null;
    }

    /**
     * 添加Cookie给reponse
     *
     * @param response
     * @param name
     * @param value
     * @param epiry        寿命，默认为-1，即持续至浏览器关闭。单位是秒。如果是0，表示立即销毁。
     * @param relativePath 相对路径（即工程路径后的路径，如“/abc”）。
     */
    public static void addCookie(HttpServletRequest request, HttpServletResponse response, String name, String value, int epiry, String relativePath) {
        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(epiry);
        cookie.setPath(request.getContextPath() + relativePath);
        response.addCookie(cookie);
    }

    /**
     * 添加Cookie给reponse
     *
     * @param response
     * @param name
     * @param value
     * @param epiry    寿命，默认为-1，即持续至浏览器关闭。单位是秒。如果是0，表示立即销毁。
     */
    public static void addCookie(HttpServletResponse response, String name, String value, int epiry) {
        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(epiry);
        response.addCookie(cookie);
    }

    /**
     * 销毁cookie
     *
     * @param response
     * @param name     cookie名
     */
    public static void terminateCookie(HttpServletResponse response, String name) {
        Cookie cookie = new Cookie(name, "");
        cookie.setMaxAge(0);
        response.addCookie(cookie);
    }

    /**
     * 获取session
     *
     * @param request
     * @return
     */
    public static HttpSession getSession(HttpServletRequest request) {
        return request.getSession();
    }

    /**
     * 设置session的失活间隔
     *
     * @param request
     * @param sec
     */
    public static void setSessionMaxInactiveInterval(HttpServletRequest request, int sec) {
        request.getSession().setMaxInactiveInterval(sec);
    }

    /**
     * 销毁session
     */
    public static void terminateSession(HttpServletRequest request) {
        request.getSession().invalidate();
    }

    /**
     * 销毁session
     *
     * @param session
     */
    public static void terminateSession(HttpSession session) {
        session.invalidate();
    }

    /**
     * 获取Session中的Kapcha验证码，需要事先引入kaptcha的依赖。
     *
     * @param request
     * @return
     */
    public static String getKaptchaToken(HttpServletRequest request) {
        // 获取session中的验证码
        String token = (String) request.getSession().getAttribute(KAPTCHA_SESSION_KEY);
        // 立刻删除session中的验证码
        request.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
        return token;
    }
}
